package cn.edu.gduf.luowowo.search.service.impl;

import cn.edu.gduf.luowowo.search.domain.StrategyTemplate;
import cn.edu.gduf.luowowo.search.query.StrategySearchQuery;
import cn.edu.gduf.luowowo.search.repository.StrategySearchRepository;
import cn.edu.gduf.luowowo.search.service.IStrategySearchService;
import cn.edu.gduf.luowowo.search.vo.StatisVO;
import com.alibaba.dubbo.config.annotation.Service;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.bucket.composite.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;

import java.util.*;

@Service
public class StrategySearchServiceImpl implements IStrategySearchService {
    @Autowired
    private StrategySearchRepository repository;
    @Autowired
    private ElasticsearchTemplate template;

    @Override
    public void saveOrUpdate(StrategyTemplate strategy) {
        repository.save(strategy);
    }

    /* 查询语句
    GET /luowowo_strategy/strategy/_search
    {
      "size":0,
      "aggs":{
        "themeGroup":{
          "composite": {
            "sources": [
              {
                "id": {
                  "terms": {
                    "field": "themeId"
                  }
                }
              },
              {
                "name": {
                  "terms": {
                    "field": "themeName"
                  }
                }
              }
            ]
          }
        }
      }
    }

    //查询结果
    {
      "took" : 1,
      "timed_out" : false,
      "_shards" : {
        "total" : 5,
        "successful" : 5,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : 17,
        "max_score" : 0.0,
        "hits" : [ ]
      },
      "aggregations" : {
        "themeGroup" : {
          "after_key" : {
            "id" : 16,
            "name" : "家庭"
          },
          "buckets" : [
            {
              "key" : {
                "id" : 3,
                "name" : "户外"
              },
              "doc_count" : 2
            },
            {
              "key" : {
                "id" : 4,
                "name" : "文化"
              }
     */
    /*//拼接上面语句中的"sources"
        List<CompositeValuesSourceBuilder<?>> sources = new ArrayList<>();
        TermsValuesSourceBuilder idSource = new TermsValuesSourceBuilder("id");
        TermsValuesSourceBuilder nameSource = new TermsValuesSourceBuilder("name");
        idSource.field("themeId");
        nameSource.field("themeName");
        sources.add(idSource);
        sources.add(nameSource);
        //拼接上面语句中的 "composite"
        //第一个参数是 自定义分组名
        //第二个参数是使用的多个分组字段，是上面的souces
        CompositeAggregationBuilder aggregationBuilder =
                new CompositeAggregationBuilder("themeGroup", sources);

        //使用这个对象来包装
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        //相当于上面语句中的 "aggs"
        builder.addAggregation(aggregationBuilder);
        builder.withPageable(PageRequest.of(0, 1));

        //都拼接好后，调用方法，相当于来执行这条语句。注意builder要.build
        //得到的结果.这里需要强转，注意，强转了才有相应的方法，Page是接口，而这个是它的实现类
        AggregatedPage<StrategyTemplate> search = (AggregatedPage) repository.search(builder.build());

        //===================上面是执行查询语句==================

        //===================下面是解析查询结果==================
        Map<String, Object> map = null;
        //拿到结果中的aggregations 中 指定分组名称的结果
        InternalComposite themeGroup = search.getAggregations().get("themeGroup");
        //再拿buckets。注意这里有坑，默认返回的类型是某个类的内部类然后不是public的无法在这里用，
        //所以用了这个内部类的父类，而这个内部类的父类也是一个内部类，不过是一个接口，接口默认是public的
        for (CompositeAggregation.Bucket bucket : themeGroup.getBuckets()) {
            //遍历这个buckets，拿到里面的数据，再封装
            //bucket.getKey()得到的是一个Map
            Long id = (Long) bucket.getKey().get("id");
            String name = (String) bucket.getKey().get("name");
            Long count = bucket.getDocCount();
            StatisVO vo = new StatisVO(id, name, count);
            map = new HashMap<>();
            map.put("theme", vo);
            //目的地(根据主题id查询它对应的所有目的地)
            List<StatisVO> dests = this.queryDestsByThemeId(id);
            map.put("dests", dests);
            list.add(map);
        }
        //排序,Collections.sort
        //第一个参数，需要排序的集合
        //第二个参数，用于定义如何排序，根据什么来排序。Compartor中的泛型是根据第一个参数集合中的泛型来决定的
        Collections.sort(list, new Comparator<Map<String, Object>>() {
            //这个是接口，实现它里面的方法
            //两个参数，可以理解为就是传进来的list集合中的两个元素
            //下面方法的返回值是int,结果大于0/小于0/等于0
            // 如果返回值小于0，就将第一个参数排在前面；大于0就将第一个参数排在后面
            //升序：v1 - v2
            //降序：v2 - v1
            @Override
            public int compare(Map<String, Object> o1, Map<String, Object> o2) {
                StatisVO v1 = (StatisVO) o1.get("theme");
                StatisVO v2 = (StatisVO) o2.get("theme");
                return (int) (v2.getCount() - v1.getCount());
            }
        });
        //前10个
        if (list.size() > 10) {
            list = list.subList(0, 10);
        }
        return list;*/
    @Override
    public List<Map<String, Object>> queryThemeTop10() {
        //最终要返回的对象
        List<Map<String, Object>> list = new ArrayList<>();
        //拿到所有的主题相关的数据，还差目的地的，已经排好序了，所以后面不需要再操作
        List<StatisVO> vos = this.statisVOGroup("themeId","themeName","themeGroup",null);
        for (StatisVO vo : vos) {
            Map<String, Object> map = new HashMap<>();
            //查询目的地
            List<StatisVO> dests = this.queryDestsByThemeId(vo.getId());
            map.put("theme",vo);
            map.put("dests",dests);
            list.add(map);
        }
        //前10个
        if (list.size() > 10) {
            list = list.subList(0, 10);
        }
        return list;
    }

    private List<StatisVO> queryDestsByThemeId(Long themeId) {
        //spring-data的jpa啥来着。 这里list中的泛型就是我们写的domain
        //对应es中的luowowo_strategy索引的strategy
        List<StrategyTemplate> list = repository.findByThemeId(themeId);
        //最终返回的结果，StatisVO中有目的地的id和name
        List<StatisVO> vos = new ArrayList<>();
        //定义一个list存放已经存在了的目的地的id或者name也可以
        List<Long> existsDests = new ArrayList<>();
        for (StrategyTemplate strategyTemplate : list) {
            //只要目的地的id，name就够了
            Long destId = strategyTemplate.getDestId();
            //去除目的地重复的
            if (!existsDests.contains(destId)) {
                String destName = strategyTemplate.getDestName();
                StatisVO vo = new StatisVO(destId, destName);
                vos.add(vo);
                existsDests.add(destId);
            }
        }
        return vos;
    }

    private List<StatisVO> statisVOGroup(String idField,String nameField,String groupName,BoolQueryBuilder query){
        //最终要返回的对象
        List<StatisVO> list = new ArrayList<>();
        //拼接上面语句中的"sources"
        List<CompositeValuesSourceBuilder<?>> sources = new ArrayList<>();
        TermsValuesSourceBuilder idSource = new TermsValuesSourceBuilder("id");
        TermsValuesSourceBuilder nameSource = new TermsValuesSourceBuilder("name");
        idSource.field(idField);
        nameSource.field(nameField);
        sources.add(idSource);
        sources.add(nameSource);
        //拼接上面语句中的 "composite"
        //第一个参数是 自定义分组名
        //第二个参数是使用的多个分组字段，是上面的souces
        CompositeAggregationBuilder aggregationBuilder =
                new CompositeAggregationBuilder(groupName, sources);
        //使用这个对象来包装
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        //相当于上面语句中的 "aggs"
        builder.addAggregation(aggregationBuilder);
        builder.withPageable(PageRequest.of(0, 1));
        if (query != null) {
            builder.withQuery(query);
        }
        AggregatedPage<StrategyTemplate> search = (AggregatedPage) repository.search(builder.build());
        //拿到结果中的aggregations 中 指定分组名称的结果
        InternalComposite themeGroup = search.getAggregations().get(groupName);
        //再拿buckets。注意这里有坑，默认返回的类型是某个类的内部类然后不是public的无法在这里用，
        //所以用了这个内部类的父类，而这个内部类的父类也是一个内部类，不过是一个接口，接口默认是public的
        for (CompositeAggregation.Bucket bucket : themeGroup.getBuckets()) {
            //遍历这个buckets，拿到里面的数据，再封装
            //bucket.getKey()得到的是一个Map
            Long id = (Long) bucket.getKey().get("id");
            String name = (String) bucket.getKey().get("name");
            Long count = bucket.getDocCount();
            StatisVO vo = new StatisVO(id, name, count);
            list.add(vo);
        }

        //排序,Collections.sort
        Collections.sort(list, new Comparator<StatisVO>() {
            @Override
            public int compare(StatisVO o1, StatisVO o2) {
                return (int) (o2.getCount()-o1.getCount());
            }
        });
        return list;
    }
    @Override
    public List<StatisVO> queryConditionTheme() {
        return this.statisVOGroup("themeId","themeName","themeGroup",null);
    }

    @Override
    public List<StatisVO> queryConditionProvince() {
        return this.statisVOGroup("provinceId","provinceName","provinceGroup",null);
    }

    @Override
    public List<StatisVO> queryConditionAbroad() {
        //只要国外的就好了
        BoolQueryBuilder query = QueryBuilders.boolQuery().
                mustNot(QueryBuilders.termQuery("countryId", 1));
        return this.statisVOGroup("countryId","countryName","countryGroup",query);
    }

    @Override
    public Page queryCondition(StrategySearchQuery qo) {
        Pageable pageable = PageRequest.of(qo.getCurrentPage() - 1, qo.getPageSize(),
                Sort.Direction.DESC, qo.getOrderBy());
        return qo.getType() == null ?
                repository.findAll(pageable):
                repository.search(QueryBuilders.termQuery(qo.getType(),qo.getTypeValue()),pageable);
    }

    @Override
    public List<StrategyTemplate> getStrategyByDestName(String destName) {
        return repository.findByDestName(destName);
    }
}
